package com.adopapa.ethexplorer.service;

import java.math.BigInteger;
import java.time.Instant;
import java.util.LinkedList;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.transaction.Transactional;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.web3j.protocol.core.methods.response.EthBlock.Block;
import org.web3j.protocol.core.methods.response.EthBlock.TransactionObject;
import org.web3j.protocol.core.methods.response.EthBlock.TransactionResult;

import com.adopapa.ethexplorer.conf.AppConfig;
import com.adopapa.ethexplorer.domain.BlockInfo;
import com.adopapa.ethexplorer.domain.TransactionInfo;
import com.adopapa.ethexplorer.utils.TimerUtil;
import com.adopapa.ethexplorer.utils.Tools;

@Service
public class WorkerService extends BaseService {

	@Autowired
	private AppConfig appConfig;
	@Autowired
	private ChaindataService chaindataService;
	@Autowired
	private BlockInfoService blockInfoService;
	@Autowired
	private TransactionInfoService transactionInfoService;

	private BigInteger lastBlockNumber;

	@PostConstruct
	public void startWork() {
		if (!appConfig.isEthSyncData()) {
			return;
		}
		logger.info("start to work...");
		BlockInfo lastBlockInfo = blockInfoService.getLastBlockInfo();
		if (Tools.empty(lastBlockInfo)) {
			lastBlockNumber = BigInteger.ZERO;
		} else {
			lastBlockNumber = BigInteger.valueOf(lastBlockInfo.getNumber());
		}

		this.doWork();
	}

	private void doWork() {
		logger.info("do sync work");
		TimerUtil.scheduleAtFixedRate(() -> {
			try {
				BigInteger currentBlockNumber = chaindataService.getBlockNumber();
				if (currentBlockNumber.compareTo(lastBlockNumber) == 1) {
					List<Block> blocks = //
							chaindataService.getBlocks(lastBlockNumber.add(BigInteger.ONE), currentBlockNumber, true);

					doWork(blocks);

					lastBlockNumber = currentBlockNumber;
				}
			} catch (Exception e) {
				logger.error(ExceptionUtils.getStackTrace(e));
			}

			return null;

		}, 10000L, 10000L);
	}

	@SuppressWarnings("unchecked")
	@Transactional
	private void doWork(List<Block> blocks) {
		
		logger.info("doWork, size: {}", blocks);

		try {

			List<BlockInfo> blockInfos = new LinkedList<BlockInfo>();
			List<TransactionInfo> transactionInfos = new LinkedList<TransactionInfo>();

			for (Block block : blocks) {
				if (Tools.empty(block)) {
					continue;
				}
//				System.out.println(block);
				blockInfos.add(new BlockInfo().hash(block.getHash())//
						.number(block.getNumber().longValue())//
						.nonce(block.getNonce().longValue())//
						.timestamp(block.getTimestamp().longValue())//
						.txCount((long) block.getTransactions().size())//
						.createTime(Instant.now()));
//				System.out.println(block.getTransactions());
				for (TransactionResult<TransactionObject> transactionResult : block.getTransactions()) {
					transactionInfos
							.add(new TransactionInfo().blockHash(transactionResult.get().getBlockHash())
									.blockNumber(transactionResult.get().getBlockNumber().longValue())//
									.from(transactionResult.get().getFrom())//
									.to(transactionResult.get().getTo())//
									.hash(transactionResult.get().getHash())//
									.nonce(transactionResult.get().getNonce().longValue())//
									.timestamp(block.getTimestamp().longValue())//
									.txIndex(transactionResult.get().getTransactionIndex().longValue())//
									.value(Tools.empty(transactionResult.get().getValue()) ? null
											: transactionResult.get().getValue().longValue())
									.createTime(Instant.now()));
				}
			}

			blockInfoService.saveBlockInfos(blockInfos);
			transactionInfoService.saveTransactionInfos(transactionInfos);

		} catch (Exception e) {
			logger.error(ExceptionUtils.getStackTrace(e));
		}

	}

}
